package elka.sag.logisticsystem.actor;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;

import org.apache.log4j.Logger;

import akka.actor.Actor;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.actor.UntypedActorFactory;
import akka.dispatch.Await;
import akka.dispatch.Future;
import akka.pattern.Patterns;
import akka.util.Duration;
import akka.util.Timeout;

import elka.sag.logisticsystem.bean.ActionStatus;
import elka.sag.logisticsystem.bean.Article;
import elka.sag.logisticsystem.bean.Order;
import elka.sag.logisticsystem.bean.Order.OrderStatus;
import elka.sag.logisticsystem.message.FactoryMessage;
import elka.sag.logisticsystem.message.ReservationMessage;
import elka.sag.logisticsystem.message.ReservationResultMessage;
import elka.sag.logisticsystem.message.SimpleMessage;

public class InventoryActor extends BaseActor {
	private static final String RULE_PATH = "inventoryRules.drl";

	private static Logger LOGGER = Logger.getLogger(InventoryActor.class);

	private List<ActorRef> warehouseActors;
	private ActorRef factoryActor;

	@Override
	public void preStart() {
		super.preStart();

		// utworzenie aktorow odpowiedzialnych za magazyny
		warehouseActors = new ArrayList<ActorRef>();
		List<String> warehouseNames = repositoryService.getWarehouseNames();
		for (String warehouseName : warehouseNames) {
			warehouseActors.add(createWarehouseActor(warehouseName));
		}

		// utworzenie aktorow odpowiedzialnego za produkcje
		factoryActor = createActor(FactoryActor.FACTORY_ACTOR_CLASS, 1);

		// zaladowanie regul drool
		ruleService.loadRules(RULE_PATH, getClass().getName());
	}

	@SuppressWarnings("serial")
	private ActorRef createWarehouseActor(final String warehouseName) {
		return this.getContext().actorOf(new Props(new UntypedActorFactory() {

			public Actor create() {
				WarehouseActor actor = null;
				try {
					actor = (WarehouseActor) Class.forName(
							WarehouseActor.WAREHOUSE_ACTOR_CLASS).newInstance();
					actor.setRepositoryService(repositoryService);
					actor.setWarehouseName(warehouseName);
				} catch (InstantiationException e) {
					LOGGER.warn("Blad tworzenie agenta obslugi magazynu " + warehouseName, e);
				} catch (IllegalAccessException e) {
					LOGGER.warn("Blad tworzenie agenta obslugi magazynu " + warehouseName, e);
				} catch (ClassNotFoundException e) {
					LOGGER.warn("Blad tworzenie agenta obslugi magazynu " + warehouseName, e);
				}
				return actor;
			}
		}));
	}

	@Override
	protected void processMessage(SimpleMessage message) throws Exception {
		//dla symulacji przetwarzania czekamy przez losowy czas 0-10s
		int waitForTime = (int) (Math.random() * 2000);
		Thread.sleep(waitForTime);
		
		if(message instanceof ReservationResultMessage) {
			LOGGER.info("Odebrano wynik rezerwacji, " + message.getOrderId());
		} else if (message instanceof SimpleMessage) {
			LOGGER.info(getClass().getSimpleName() + ": Kompletowanie zamowienia, orderId: " + message.getOrderId());
			ActionStatus actionStatus = ActionStatus.OK;
			
			Order order = repositoryService.getOrderById(message.getOrderId());
			//towary do zarezerwowania
			List<Article> orderArticles = repositoryService.getOrderArticles(order.getId());
			//towary do wyprodukowania
			List<Article> articlesToFactory = new ArrayList<Article>();
			
			//rezerwuj kazdy z towarow
			for(int i=0 ; i<orderArticles.size() ; ++i) {
				//wyslij komunikat o rezerwacji do wszystki agentow-magazynow
				//utworzenie komunikatu rezerwacji
				ReservationMessage msg = new ReservationMessage(order.getId(), orderArticles.get(i));
				
				if(!orderArticles.get(i).isReserved()) {
					//odpytaj magazyny
					for(ActorRef actorRef : warehouseActors) {
						try {
							Timeout timeout = new Timeout(Duration.create(2, "seconds"));
						    Future<Object> future = Patterns.ask(actorRef, msg, timeout);
						    ReservationResultMessage result = (ReservationResultMessage) Await.result(future, timeout.duration());
						    //jak w result b�dzie true to nie musisz dalej lecie� po petli
						    LOGGER.info(getClass().getSimpleName() + ": Otrzymano odpowiedz dla rezerwacji towaru (orderId: " +  result.getOrderId() + ") , towar zarezerwowano: " + result.isReserved());
						    
						    if(result.isReserved()) {//jesli udalo sie zarezerwowac to ustaw flage
						    	LOGGER.info(getClass().getSimpleName() + ": Zarezerwowano towar, idTowaru: " + orderArticles.get(i).getName() + ", orderId: " + order.getId());
						    	orderArticles.get(i).setReserved(true);
						    	break; //koniec nie trzeba dalej odpytawac magazynow
						    }
						} catch (TimeoutException e) {
							LOGGER.info("Timeout podczas rezerwacji towarow w magazynie");
						}
					}
					
					//jezeli nie udalo sie zarezerwowac to dodaj do listy 
					if(!orderArticles.get(i).isReserved()) {
						articlesToFactory.add(orderArticles.get(i));
					}
				}
			}

			//zlec wyprodukowanie towarow
			if(!articlesToFactory.isEmpty()) {
				LOGGER.info(getClass().getSimpleName() + ": Wyslanie zlecenia produkcji towarow dla zamowienia, orderId: " + order.getId() + ", liczba towarow: " + articlesToFactory.size());
				FactoryMessage factoryMessage = new FactoryMessage(message.getOrderId(), articlesToFactory);
				factoryActor.tell(factoryMessage);
				actionStatus = ActionStatus.FACTORY;
			}
			
			OrderStatus newOrderStatus = getNextOrderStatus(order, actionStatus);
			
            repositoryService.setOrderArticles(orderArticles, order.getId());
            
			order.setStatus(newOrderStatus);
			order.setProcess(false);
			repositoryService.updateOrderStatusAndProcess(order);
		}
	}
}
